Please follow the instructions in the README file before starting this tutorial.


This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.


Install missing packages

  • First check that we have all the packages that we need for the tutorial
  • if any are missing then they will be installed
if (!require(tidyverse)) install.packages("tidyverse")
if (!require(readxl)) install.packages("readxl")
if (!require(RSQLite)) install.packages("RSQLite")
if (!require(DT)) install.packages("DT")
if (!require(plotly)) install.packages("plotly")
if (!require(ggsignif)) install.packages("ggsignif")
if (!require(ggdendro)) install.packages("ggdendro")
if (!require(patchwork)) install.packages("patchwork")

Loading the packages

  • We will use the various packages of the tidyverse
  • This includes the popular plotting library ggplot2
  • Lets load the packages
library(tidyverse)

Loading the data

  • We now need load some data to plot

  • We can load this from most of the popular formats

  • From a comma separated formated file (csv):

read_csv('data/stats.csv', show_col_types = FALSE)
  • From a tab separated formated file (tsv):
read_tsv('data/stats.tsv', show_col_types = FALSE)
  • From an excel file, in order to do this we need to first load the readxl package
library(readxl)

read_xlsx('data/stats.xlsx')
  • We can also load data directly from a database, to access a local sqlite3 database, first load the RSQLite package (to load from MIcrosoft Access you could use the RODBC package)
library(RSQLite)

conn <- dbConnect(RSQLite::SQLite(), "data/stats.db")

dbGetQuery(conn, "SELECT * FROM stats")
  • Above we loaded the data and printed it to the screen. But we need to keep the data in a variable:
stats <- read_xlsx('data/stats.xlsx')
  • the data is now stored in the stats variable, we can verify this by just running the variable name
stats
  • We can generate nicer looking tables for our reports using the DT package and the datatable function
library(DT)

datatable(stats)
  • if we need to we can use the pipe symbol %>% to run other filtering and renaming functions on our data, here we use the filter() function to filter the data by the reads column. Notice that the number of rows reduces from 58 to 21
stats %>%
  filter(reads > 1000000)
  • here we filter by both the reads and the coverage columns, we now only have 8 rows
stats %>%
  filter(reads > 1000000, coverage > 110)
  • here we use the mutate() function to add a new column called total by calculating the sum of the coverage and the reads columns
stats %>%
  mutate(total = reads + coverage)
  • here we use the rename() function to change the reads column name to counts
stats %>%
  rename(counts = reads)

Plotting with ggplot

Plotting a bar charts

  • ggplot allows us to generate figures in layers, building them up piece by piece
  • initially we generate the coordinate system
  • ggplot() need the name of the variable containing the data and which variables to use in the plot
ggplot(stats, aes(x=isolate, y=reads)) 

  • there is no actually plot here as we have not yet told ggplot which kind of plot we want
  • we add the layers by simply using the ‘+’ operator. Here we specify a bar chart. By default geom_bar() will calculate the height of the bars from the data, but if we want to specify the data for the y-axis we need to supply stat="identity"
ggplot(stats, aes(x=isolate, y=reads)) +
  geom_bar(stat="identity")

  • we can easily specifiy some colours
ggplot(stats, aes(x=isolate, y=reads)) +
  geom_bar(stat="identity", colour='black', fill='blue')

ggplot(stats, aes(x=isolate, y=reads)) +
    geom_bar(stat="identity", colour='#ff0066', fill='#cc0000')

  • We can change other stylistic parts of the chart with themes, we can set the general theme with some prebuilt styles e.g. theme_bw(), theme_minimal(), theme_classic(); more details and examples can be found at https://ggplot2.tidyverse.org/reference/ggtheme.html
  • Lets try out the theme_bw() theme
ggplot(stats, aes(x=isolate, y=reads)) +
    geom_bar(stat="identity", colour='black', fill='blue') +
    theme_bw()

  • the x-axis labels overlap and are unreadable, so lets rotate them using theme()
ggplot(stats, aes(x=isolate, y=reads)) +
  geom_bar(stat="identity", colour='black', fill='blue') +
  theme_bw() +
  theme(
    axis.text.x = element_text(angle=-90)
  )

  • the x-axis labels are not quite aligned to the tick marks, we can adjust them with the vjust and hjust parameters
ggplot(stats, aes(x=isolate, y=reads)) +
  geom_bar(stat="identity", colour='black', fill='blue') +
  theme_bw() +
  theme(
    axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5)
  )

  • we can now change the axes labels (labs) and add a title (ggtitle), change the font and font sizes of the axis labels
ggplot(stats, aes(x=isolate, y=reads)) +
  geom_bar(stat="identity", colour='black', fill='blue') +
  ggtitle("Read counts") +
  labs(x="Isolate name", y="Read count") +  theme_bw() +
  theme(
    axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5, size=10),
    axis.title  = element_text(size=15, family='Comic Sans MS')
  )

  • if we prefer we can flip the whole chart
ggplot(stats, aes(x=isolate, y=reads)) +
  geom_bar(stat="identity", colour='black', fill='blue') +
  ggtitle("Read counts") +
  labs(x="Isolate name", y="Read count") +  theme_bw() +
  theme(
    axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5)
  ) +
  coord_flip()

  • in the above examples we provided the stats data object to the ggplot() function. Very often its easier to pipe the data into the ggplot() function using %>%. As you can see, the stats variable name is now outside the ggplot() function
stats %>%
  ggplot(aes(x=isolate, y=reads)) +
    geom_bar(stat="identity", colour='black', fill='blue') +
    ggtitle("Read counts") +
    labs(x="Isolate name", y="Read count") +  theme_bw() +
    theme(
      axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5)
    )

  • This allows us to manipulate the data before we plot it, for example we can apply a filter and only plot certain samples. grepl() is just a pattern matching function, so filters isolates with the string SER in their name
stats %>%
  filter(grepl("SER",isolate)) %>%
  ggplot(aes(x=isolate, y=reads)) +
    geom_bar(stat="identity", colour='black', fill='blue') +
    ggtitle("Read counts") +
    labs(x="Isolate name", y="Read count") +
    theme_bw()

  • now that we have a smaller plot, we can also add other useful information, like the actual values of the bars, using geom_text()
stats %>%
  filter(grepl("SER",isolate)) %>%
  ggplot(aes(x=isolate, y=reads)) +
    geom_bar(stat="identity", colour='black', fill='blue') +
    ggtitle("Read counts") +
    labs(x="Isolate name", y="Read count") +
    geom_text(aes(label=reads), vjust=-0.3, size=3.5) +
    theme_bw()


Plotting a scatter plot

  • Lets plot a different type of graph, this time plotting reads against coverage
stats %>%
  ggplot(aes(x=reads, y=coverage)) +
    geom_point() +
    theme_bw() +
    theme(
      axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5)
    ) 

  • we can easily add a smoothed regression line with geom_smooth()
stats %>%
  ggplot(aes(x=reads, y=coverage)) +
    geom_point() +
    geom_smooth() +
    theme_bw() +
    theme(
      axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5)
    ) 
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

  • if we need to we can limit the region of the plot to include, using xlim() and/or ylim()
stats %>%
  ggplot(aes(x=reads, y=coverage)) +
    geom_point() +
    geom_smooth() +
    xlim(c(0, 600000)) + 
    ylim(c(0, 100)) +
    theme_bw() +
    theme(
      axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5)
    ) 
`geom_smooth()` using method = 'loess' and formula 'y ~ x'
Warning: Removed 21 rows containing non-finite values (stat_smooth).
Warning: Removed 21 rows containing missing values (geom_point).

  • Finally if we wish we can make interactive versions of our plots using the plotly package
  • try selection regions of the plot and zooming, using the menu ribbon on the top right of the plot
library(plotly)

coverage <- stats %>%
  ggplot(aes(x=reads, y=coverage)) +
    geom_point() +
    geom_smooth() +
    xlim(c(0, 600000)) + 
    ylim(c(0, 100)) +
    theme_bw() +
    theme(
      axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5)
    ) 

ggplotly(coverage)
`geom_smooth()` using method = 'loess' and formula 'y ~ x'
Warning: Removed 21 rows containing non-finite values (stat_smooth).

Exercise 1

  • load the dataset stored in data/data.csv, plot a bar chart of county against popadults for the state of Ohio (OH)
  • remember how to rotate the x axis labels to make them more readable,
  • add a title to the plot and rename the axes titles
  • Using the same dataset generate a scatter plot of area against poptotal
  • Try adding a regression line using the glm method
  • Try removing some of the outliers from the plot. Hint, use xlim() and/or ylim()
  • Change the size and colour of the points? Hint: check the help with ?geom_line
  • Colour the points by the state column
  • Set the size of the points according to the popdensity column
  • Move the legend position to the bottom of the chart. Hint: check the help with ?theme
  • Add title and a subtitle
  • Change the axes titles to ‘Population total’ and ‘County area’
  • Improve the labels on the y-axis. Hint: look at ?scale_y_continuous

Plotting boxplots

  • We will use one of the built in datasets for this section, the dataset shows the effect of Vitamin C on tooth growth via orange juice (OJ) or ascorbic acid (VC). Explore the data
ToothGrowth
  • Lets do an initial scatter plot of dose against len colouring by supp
ToothGrowth %>%
  ggplot(aes(x=dose, y=len, color=supp)) +
    geom_point() +
    theme_bw()

  • this is not that easy to visualise so lets jitter (separate) the points a little by replacing geom_point with geom_jitter
ToothGrowth %>%
  ggplot(aes(x=dose, y=len, group=supp, color=supp)) +
    geom_jitter(width=0.1) +
    theme_bw()

  • In order to plot a boxplot the x-axis needs to be categorical data, so we can convert our dose values to characters using the mutate() and as.character() functions
ToothGrowth %>%
  mutate(dose=as.character(dose)) %>%
  ggplot(aes(x=dose, y=len, color=supp)) +
    geom_boxplot(color='black') +
    theme_bw()

  • Sometimes it is useful to keep the individual points on the plot, we can just add back the jittered layer
ToothGrowth %>%
  mutate(dose=as.character(dose)) %>%
  ggplot(aes(x=dose, y=len, color=supp)) +
    geom_boxplot(color='black') +
    geom_jitter(width=0.1)  +
    theme_bw()

  • we could even further break the boxplots down by the supp variable, by removing color="black" from geom_boxplot()
ToothGrowth %>%
  mutate(dose=as.character(dose)) %>%
  ggplot(aes(x=dose, y=len, color=supp)) +
    geom_boxplot() +
    geom_jitter(width=0.1)  +
    theme_bw()

  • lets tidy up the plot, by labeling the axes and the legend properly
ToothGrowth %>%
  mutate(dose=as.character(dose)) %>%
  ggplot(aes(x=dose, y=len, color=supp)) +
    geom_boxplot(color='black') +
    geom_jitter(width=0.1)  +
    labs(x="Vitamin C dose", y="Tooth length") +
    theme_bw() +
    theme(
      legend.title = element_blank()    # this removes the legend title
    )

  • And if we want to add some significance labels, we can make use of the ggsignif package
library(ggsignif)

ToothGrowth %>%
  mutate(dose=as.character(dose)) %>%
  ggplot(aes(x=dose, y=len, color=supp)) +
    geom_boxplot(color='black') +
    geom_jitter(width=0.1)  +
    labs(x="Vitamin C dose", y="Tooth length") +
    theme_bw() +
    theme(
      legend.title = element_blank()    # this removes the legend title
    ) +
    ylim(0,40) +
    geom_signif(comparisons = list(c("0.5", "1")), map_signif_level = TRUE, textsize = 6, y_position = 30, colour="black", annotation=c('**')) +
    geom_signif(comparisons = list(c("1", "2")), map_signif_level = TRUE, textsize = 6, y_position = 36, colour="black", annotation=c('*')) 


Plotting line plots

  • Using the same tooth growth dataset, in order to draw a line plot we need to calculate the means and standard deviations of the data. Remember the data looks like this
ToothGrowth
  • we can summarise the data by grouping the variables and calculating the mean/sd over these variables
summary <- ToothGrowth %>%
  group_by(supp, dose) %>%
  summarise(mean=mean(len), sd=sd(len))
`summarise()` has grouped output by 'supp'. You can override using the `.groups` argument.
summary
  • it is now straightforward to generate a line plot
summary %>%
  ggplot(aes(x=dose, y=mean, color=supp)) +
    geom_line() +
    theme_bw()

  • we can also use the calculated standard deviations to add error bars
summary %>%
  ggplot(aes(x=dose, y=mean, color=supp)) +
    geom_line() +
    geom_point() +
    geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.05) +
    theme_bw()

  • the error bars overlap so we can actually shift (dodge) them slightly
summary %>%
  ggplot(aes(x=dose, y=mean, color=supp)) +
    geom_line() +
    geom_point() +
    geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.05,
                    position=position_dodge(0.05)) +
    theme_bw()


Exercise 2

  • We will use another default dataset, mtcars. First take a look and explore
  • Plot a boxplot with cyl on the x-axis and mpg on the y-axis
  • include the raw data points, jittered and coloured by carb
  • Tidy up the axes labels and remove the legend title
  • Now plot a line graph showing the mean and the standard deviations of the mpg values, coloured by the number of carburetors
  • Plot a similar plot comparing the number oc cylinders with the mpg but coloured by the number of gears

Plotting dendrograms

  • We can easily perform hierarchical clustering on a dataset and then draw a tree
  • here we use the built in dataset of USarrests, first explore the data
USArrests
  • Now we can perform the clustering using the hclust function, this uses euclidean distances by default
  • But the distance object (dist(USArrests)) can be any kind of similarity matrix, e.g pearson correlations
hc <- hclust(dist(USArrests), "ave")  # hierarchical clustering
hc

Call:
hclust(d = dist(USArrests), method = "ave")

Cluster method   : average 
Distance         : euclidean 
Number of objects: 50 
  • Now we draw the tree using the ggdendrogram() function from the `ggdendro package
library(ggdendro)

p <- ggdendrogram(hc, rotate = TRUE, size = 2)
p

  • remember that we can easily make the plot interactive with ggplotly()
ggplotly(p)

Combining plots into a figure

  • In order to combine plots into a publication ready figure, lets plot both the previous reads and the coverage plots, only this time we will store the plots in the variables reads and coverage, notice that they won’t be printed when you run the code
reads <- stats %>%
  ggplot(aes(x=isolate, y=reads)) +
    geom_bar(stat="identity", colour='black', fill='blue') +
    ggtitle("Read counts") +
    labs(x="Isolate name", y="Read count") +  theme_bw() +
    theme(
      axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5)
    )

coverage <- stats %>%
  ggplot(aes(x=isolate, y=coverage)) +
    geom_bar(stat="identity", colour='black', fill='#888888') +
    ggtitle("Coverage") +
    labs(x="Isolate name", y="Coverage") +
    theme_bw() +
    theme(
      axis.text.x = element_text(angle=-90, hjust=1, vjust=0.5)
    )
  • but we can show the plots by just “running” the variable names
reads

coverage

  • we can now make a combined plot usingt the patchwork package
  • firstly side by side with the ‘+’ operator
library(patchwork)

reads + coverage

  • if we want them above and below we change the ‘+’ operator to ‘/’
reads / coverage

  • We can add subplot labels (A, B etc)
reads / coverage + plot_annotation(tag_levels = 'A') + plot_layout(guides = "collect")

  • Note that we can include more plots in the figure. Here we generate four versions of the reads plot but changing the underlying theme. We can also add a new title, note that this is overriding the ggtitle() already in the reads plot. Can you spot the differences in the themes?

p1 <- reads + theme_bw() + ggtitle('theme_bw()')
p2 <- reads + theme_classic() + ggtitle('theme_classic()')
p3 <- reads + theme_minimal() + ggtitle('theme_minimal()')
p4 <- reads + theme_dark() + ggtitle('theme_dark()')

( p1 + p2 ) / ( p3 + p4 ) + plot_annotation(tag_levels = 'A')


Exercise 3

  • Generate a combined figure including a boxplot and a lineplot comparing the number of cylinders with the mpg as generated above, label the subplots using a, b

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.


Session details

  • Generate document version details
sessionInfo()
R version 4.1.1 (2021-08-10)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: CentOS Linux 7 (Core)

Matrix products: default
BLAS:   /opt/R/4.1.1/lib64/R/lib/libRblas.so
LAPACK: /opt/R/4.1.1/lib64/R/lib/libRlapack.so

locale:
 [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C               LC_TIME=en_GB.UTF-8       
 [4] LC_COLLATE=en_GB.UTF-8     LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
 [7] LC_PAPER=en_GB.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ggsignif_0.6.3  ggdendro_0.1.23 gapminder_0.3.0 gganimate_1.0.7 plotly_4.10.0   patchwork_1.1.1
 [7] DT_0.23         readxl_1.3.1    forcats_0.5.1   stringr_1.4.0   dplyr_1.0.7     purrr_0.3.4    
[13] readr_2.0.1     tidyr_1.1.3     tibble_3.1.4    ggplot2_3.3.5   tidyverse_1.3.1 RSQLite_2.2.14 

loaded via a namespace (and not attached):
 [1] nlme_3.1-152      fs_1.5.0          lubridate_1.7.10  bit64_4.0.5       progress_1.2.2   
 [6] httr_1.4.2        tools_4.1.1       backports_1.2.1   bslib_0.3.1       utf8_1.2.2       
[11] R6_2.5.1          DBI_1.1.1         lazyeval_0.2.2    mgcv_1.8-36       colorspace_2.0-2 
[16] withr_2.4.2       tidyselect_1.1.1  prettyunits_1.1.1 bit_4.0.4         compiler_4.1.1   
[21] cli_3.0.1         rvest_1.0.1       xml2_1.3.2        labeling_0.4.2    sass_0.4.1       
[26] scales_1.1.1      digest_0.6.27     rmarkdown_2.11    pkgconfig_2.0.3   htmltools_0.5.2  
[31] dbplyr_2.1.1      fastmap_1.1.0     htmlwidgets_1.5.4 rlang_0.4.11      rstudioapi_0.13  
[36] jquerylib_0.1.4   farver_2.1.0      generics_0.1.0    jsonlite_1.7.2    crosstalk_1.2.0  
[41] vroom_1.5.5       magrittr_2.0.1    Matrix_1.3-4      Rcpp_1.0.7        munsell_0.5.0    
[46] fansi_0.5.0       lifecycle_1.0.0   stringi_1.7.4     yaml_2.2.1        MASS_7.3-54      
[51] plyr_1.8.7        grid_4.1.1        blob_1.2.2        parallel_4.1.1    crayon_1.4.1     
[56] lattice_0.20-44   haven_2.4.3       splines_4.1.1     hms_1.1.0         knitr_1.34       
[61] pillar_1.6.2      reprex_2.0.1      glue_1.4.2        evaluate_0.14     data.table_1.14.0
[66] modelr_0.1.8      vctrs_0.3.8       tzdb_0.1.2        tweenr_1.0.2      cellranger_1.1.0 
[71] gtable_0.3.0      assertthat_0.2.1  cachem_1.0.6      xfun_0.26         broom_0.7.9      
[76] viridisLite_0.4.0 memoise_2.0.1     ellipsis_0.3.2   
LS0tCnRpdGxlOiAiVXNpbmcgZ2dwbG90IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgaGlnaGxpZ2h0OiB6ZW5idXJuCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vCiAgICB0aGVtZTogY2VydWxlYW4KICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCioqX1BsZWFzZSBmb2xsb3cgdGhlIGluc3RydWN0aW9ucyBpbiB0aGUgUkVBRE1FIGZpbGUgYmVmb3JlIHN0YXJ0aW5nIHRoaXMgdHV0b3JpYWwuXyoqCgo8aHIgc3R5bGU9ImJvcmRlcjoycHggc29saWQgZ3JheSI+IDwvaHI+CgpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiAKClRyeSBleGVjdXRpbmcgdGhpcyBjaHVuayBieSBjbGlja2luZyB0aGUgKlJ1biogYnV0dG9uIHdpdGhpbiB0aGUgY2h1bmsgb3IgYnkgcGxhY2luZyB5b3VyIGN1cnNvciBpbnNpZGUgaXQgYW5kIHByZXNzaW5nICpDdHJsK1NoaWZ0K0VudGVyKi4gCgo8aHIgc3R5bGU9ImJvcmRlcjoycHggc29saWQgZ3JheSI+IDwvaHI+CgojIEluc3RhbGwgbWlzc2luZyBwYWNrYWdlcwoKKiBGaXJzdCBjaGVjayB0aGF0IHdlIGhhdmUgYWxsIHRoZSBwYWNrYWdlcyB0aGF0IHdlIG5lZWQgZm9yIHRoZSB0dXRvcmlhbAoqIGlmIGFueSBhcmUgbWlzc2luZyB0aGVuIHRoZXkgd2lsbCBiZSBpbnN0YWxsZWQKCmBgYHtyfQppZiAoIXJlcXVpcmUodGlkeXZlcnNlKSkgaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikKaWYgKCFyZXF1aXJlKHJlYWR4bCkpIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpCmlmICghcmVxdWlyZShSU1FMaXRlKSkgaW5zdGFsbC5wYWNrYWdlcygiUlNRTGl0ZSIpCmlmICghcmVxdWlyZShEVCkpIGluc3RhbGwucGFja2FnZXMoIkRUIikKaWYgKCFyZXF1aXJlKHBsb3RseSkpIGluc3RhbGwucGFja2FnZXMoInBsb3RseSIpCmlmICghcmVxdWlyZShnZ3NpZ25pZikpIGluc3RhbGwucGFja2FnZXMoImdnc2lnbmlmIikKaWYgKCFyZXF1aXJlKGdnZGVuZHJvKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dkZW5kcm8iKQppZiAoIXJlcXVpcmUocGF0Y2h3b3JrKSkgaW5zdGFsbC5wYWNrYWdlcygicGF0Y2h3b3JrIikKYGBgCgo8aHIgc3R5bGU9ImJvcmRlcjoycHggc29saWQgZ3JheSI+IDwvaHI+CgojIExvYWRpbmcgdGhlIHBhY2thZ2VzCgoqIFdlIHdpbGwgdXNlIHRoZSB2YXJpb3VzIHBhY2thZ2VzIG9mIHRoZSBbdGlkeXZlcnNlXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykKKiBUaGlzIGluY2x1ZGVzIHRoZSBwb3B1bGFyIHBsb3R0aW5nIGxpYnJhcnkgZ2dwbG90MgoqIExldHMgbG9hZCB0aGUgcGFja2FnZXMKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgojIExvYWRpbmcgdGhlIGRhdGEKCiogV2Ugbm93IG5lZWQgbG9hZCBzb21lIGRhdGEgdG8gcGxvdAoqIFdlIGNhbiBsb2FkIHRoaXMgZnJvbSBtb3N0IG9mIHRoZSBwb3B1bGFyIGZvcm1hdHMKCiogRnJvbSBhIGNvbW1hIHNlcGFyYXRlZCBmb3JtYXRlZCBmaWxlIChjc3YpOgoKYGBge3J9CnJlYWRfY3N2KCdkYXRhL3N0YXRzLmNzdicsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpCmBgYAoKKiBGcm9tIGEgdGFiIHNlcGFyYXRlZCBmb3JtYXRlZCBmaWxlICh0c3YpOgoKYGBge3J9CnJlYWRfdHN2KCdkYXRhL3N0YXRzLnRzdicsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpCmBgYAoKKiBGcm9tIGFuIGV4Y2VsIGZpbGUsIGluIG9yZGVyIHRvIGRvIHRoaXMgd2UgbmVlZCB0byBmaXJzdCBsb2FkIHRoZSBgcmVhZHhsYCBwYWNrYWdlCgpgYGB7cn0KbGlicmFyeShyZWFkeGwpCgpyZWFkX3hsc3goJ2RhdGEvc3RhdHMueGxzeCcpCmBgYAoKKiBXZSBjYW4gYWxzbyBsb2FkIGRhdGEgZGlyZWN0bHkgZnJvbSBhIGRhdGFiYXNlLCB0byBhY2Nlc3MgYSBsb2NhbCBzcWxpdGUzIGRhdGFiYXNlLCBmaXJzdCBsb2FkIHRoZSBgUlNRTGl0ZWAgcGFja2FnZSAodG8gbG9hZCBmcm9tIE1JY3Jvc29mdCBBY2Nlc3MgeW91IGNvdWxkIHVzZSB0aGUgYFJPREJDYCBwYWNrYWdlKQoKYGBge3J9CmxpYnJhcnkoUlNRTGl0ZSkKCmNvbm4gPC0gZGJDb25uZWN0KFJTUUxpdGU6OlNRTGl0ZSgpLCAiZGF0YS9zdGF0cy5kYiIpCgpkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBGUk9NIHN0YXRzIikKYGBgCgoqIEFib3ZlIHdlIGxvYWRlZCB0aGUgZGF0YSBhbmQgcHJpbnRlZCBpdCB0byB0aGUgc2NyZWVuLiBCdXQgd2UgbmVlZCB0byBrZWVwIHRoZSBkYXRhIGluIGEgdmFyaWFibGU6CgpgYGB7cn0Kc3RhdHMgPC0gcmVhZF94bHN4KCdkYXRhL3N0YXRzLnhsc3gnKQpgYGAKCiogdGhlIGRhdGEgaXMgbm93IHN0b3JlZCBpbiB0aGUgYHN0YXRzYCB2YXJpYWJsZSwgd2UgY2FuIHZlcmlmeSB0aGlzIGJ5IGp1c3QgcnVubmluZyB0aGUgdmFyaWFibGUgbmFtZQoKYGBge3J9CnN0YXRzCmBgYAoKKiBXZSBjYW4gZ2VuZXJhdGUgbmljZXIgbG9va2luZyB0YWJsZXMgZm9yIG91ciByZXBvcnRzIHVzaW5nIHRoZSBgRFRgIHBhY2thZ2UgYW5kIHRoZSBgZGF0YXRhYmxlYCBmdW5jdGlvbgoKYGBge3J9CmxpYnJhcnkoRFQpCgpkYXRhdGFibGUoc3RhdHMpCmBgYAoKKiBpZiB3ZSBuZWVkIHRvIHdlIGNhbiB1c2UgdGhlIHBpcGUgc3ltYm9sIGAlPiVgIHRvIHJ1biBvdGhlciBmaWx0ZXJpbmcgYW5kIHJlbmFtaW5nIGZ1bmN0aW9ucyBvbiBvdXIgZGF0YSwgaGVyZSB3ZSB1c2UgdGhlIGBmaWx0ZXIoKWAgZnVuY3Rpb24gdG8gZmlsdGVyIHRoZSBkYXRhIGJ5IHRoZSBgcmVhZHNgIGNvbHVtbi4gTm90aWNlIHRoYXQgdGhlIG51bWJlciBvZiByb3dzIHJlZHVjZXMgZnJvbSA1OCB0byAyMQoKYGBge3J9CnN0YXRzICU+JQogIGZpbHRlcihyZWFkcyA+IDEwMDAwMDApCmBgYAoKKiBoZXJlIHdlIGZpbHRlciBieSBib3RoIHRoZSBgcmVhZHNgIGFuZCB0aGUgYGNvdmVyYWdlYCBjb2x1bW5zLCB3ZSBub3cgb25seSBoYXZlIDggcm93cwoKYGBge3J9CnN0YXRzICU+JQogIGZpbHRlcihyZWFkcyA+IDEwMDAwMDAsIGNvdmVyYWdlID4gMTEwKQpgYGAKCiogaGVyZSB3ZSB1c2UgdGhlIGBtdXRhdGUoKWAgZnVuY3Rpb24gdG8gYWRkIGEgbmV3IGNvbHVtbiBjYWxsZWQgYHRvdGFsYCBieSBjYWxjdWxhdGluZyB0aGUgc3VtIG9mIHRoZSBgY292ZXJhZ2VgIGFuZCB0aGUgYHJlYWRzYCBjb2x1bW5zCgoKYGBge3J9CnN0YXRzICU+JQogIG11dGF0ZSh0b3RhbCA9IHJlYWRzICsgY292ZXJhZ2UpCmBgYAoKKiBoZXJlIHdlIHVzZSB0aGUgYHJlbmFtZSgpYCBmdW5jdGlvbiB0byBjaGFuZ2UgdGhlIGByZWFkc2AgY29sdW1uIG5hbWUgdG8gYGNvdW50c2AKCgpgYGB7cn0Kc3RhdHMgJT4lCiAgcmVuYW1lKGNvdW50cyA9IHJlYWRzKQpgYGAKCjxociBzdHlsZT0iYm9yZGVyOjJweCBzb2xpZCBncmF5Ij4gPC9ocj4KCiMgUGxvdHRpbmcgd2l0aCBnZ3Bsb3QKCiMjIFBsb3R0aW5nIGEgYmFyIGNoYXJ0cwoKKiBnZ3Bsb3QgYWxsb3dzIHVzIHRvIGdlbmVyYXRlIGZpZ3VyZXMgaW4gbGF5ZXJzLCBidWlsZGluZyB0aGVtIHVwIHBpZWNlIGJ5IHBpZWNlCiogaW5pdGlhbGx5IHdlIGdlbmVyYXRlIHRoZSBjb29yZGluYXRlIHN5c3RlbQoqIGBnZ3Bsb3QoKWAgbmVlZCB0aGUgbmFtZSBvZiB0aGUgdmFyaWFibGUgY29udGFpbmluZyB0aGUgZGF0YSBhbmQgd2hpY2ggdmFyaWFibGVzIHRvIHVzZSBpbiB0aGUgcGxvdAoKYGBge3IgZmlnLndpZHRoPTEwfQpnZ3Bsb3Qoc3RhdHMsIGFlcyh4PWlzb2xhdGUsIHk9cmVhZHMpKSAKYGBgCgoqIHRoZXJlIGlzIG5vIGFjdHVhbGx5IHBsb3QgaGVyZSBhcyB3ZSBoYXZlIG5vdCB5ZXQgdG9sZCBnZ3Bsb3Qgd2hpY2gga2luZCBvZiBwbG90IHdlIHdhbnQKKiB3ZSBhZGQgdGhlIGxheWVycyBieSBzaW1wbHkgdXNpbmcgdGhlICcrJyBvcGVyYXRvci4gSGVyZSB3ZSBzcGVjaWZ5IGEgYmFyIGNoYXJ0LiBCeSBkZWZhdWx0IGBnZW9tX2JhcigpYCB3aWxsIGNhbGN1bGF0ZSB0aGUgaGVpZ2h0IG9mIHRoZSBiYXJzIGZyb20gdGhlIGRhdGEsIGJ1dCBpZiB3ZSB3YW50IHRvIHNwZWNpZnkgdGhlIGRhdGEgZm9yIHRoZSB5LWF4aXMgd2UgbmVlZCB0byBzdXBwbHkgYHN0YXQ9ImlkZW50aXR5ImAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KHN0YXRzLCBhZXMoeD1pc29sYXRlLCB5PXJlYWRzKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikKYGBgCgoqIHdlIGNhbiBlYXNpbHkgc3BlY2lmaXkgc29tZSBjb2xvdXJzCgpgYGB7ciBmaWcud2lkdGg9MTB9CmdncGxvdChzdGF0cywgYWVzKHg9aXNvbGF0ZSwgeT1yZWFkcykpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGNvbG91cj0nYmxhY2snLCBmaWxsPSdibHVlJykKYGBgCgoqIGNvbW1vbiBjb2xvdXJzIGNhbiBiZSBkZWZpbmVkIHNpbXBseSBieSBuYW1lIGFzIGFib3ZlLCBidXQgbW9yZSBjb250cm9sIGNhbiBiZSBnYWluZWQgYnkgc3BlY2lmeWluZyBodG1sIHdlYiBjb2xvdXJzLCBhIGNvbG91ciBzZWxlY3RvciBjYW4gYmUgZm91bmQgaGVyZSBodHRwczovL3d3dy53M3NjaG9vbHMuY29tL2NvbG9ycy9jb2xvcnNfcGlja2VyLmFzcAoKYGBge3IgZmlnLndpZHRoPTEwfQpnZ3Bsb3Qoc3RhdHMsIGFlcyh4PWlzb2xhdGUsIHk9cmVhZHMpKSArCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGNvbG91cj0nI2ZmMDA2NicsIGZpbGw9JyNjYzAwMDAnKQpgYGAKCiogV2UgY2FuIGNoYW5nZSBvdGhlciBzdHlsaXN0aWMgcGFydHMgb2YgdGhlIGNoYXJ0IHdpdGggdGhlbWVzLCB3ZSBjYW4gc2V0IHRoZSBnZW5lcmFsIHRoZW1lIHdpdGggc29tZSBwcmVidWlsdCBzdHlsZXMgZS5nLiBgdGhlbWVfYncoKWAsIGB0aGVtZV9taW5pbWFsKClgLCBgdGhlbWVfY2xhc3NpYygpYDsgbW9yZSBkZXRhaWxzIGFuZCBleGFtcGxlcyBjYW4gYmUgZm91bmQgYXQgaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2dndGhlbWUuaHRtbAoqIExldHMgdHJ5IG91dCB0aGUgYHRoZW1lX2J3KClgIHRoZW1lCgpgYGB7ciBmaWcud2lkdGg9MTB9CmdncGxvdChzdGF0cywgYWVzKHg9aXNvbGF0ZSwgeT1yZWFkcykpICsKICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycsIGZpbGw9J2JsdWUnKSArCiAgICB0aGVtZV9idygpCmBgYAoKKiB0aGUgeC1heGlzIGxhYmVscyBvdmVybGFwIGFuZCBhcmUgdW5yZWFkYWJsZSwgc28gbGV0cyByb3RhdGUgdGhlbSB1c2luZyBgdGhlbWUoKWAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KHN0YXRzLCBhZXMoeD1pc29sYXRlLCB5PXJlYWRzKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycsIGZpbGw9J2JsdWUnKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0tOTApCiAgKQpgYGAKCiogdGhlIHgtYXhpcyBsYWJlbHMgYXJlIG5vdCBxdWl0ZSBhbGlnbmVkIHRvIHRoZSB0aWNrIG1hcmtzLCB3ZSBjYW4gYWRqdXN0IHRoZW0gd2l0aCB0aGUgYHZqdXN0YCBhbmQgYGhqdXN0YCBwYXJhbWV0ZXJzCgpgYGB7ciBmaWcud2lkdGg9MTB9CmdncGxvdChzdGF0cywgYWVzKHg9aXNvbGF0ZSwgeT1yZWFkcykpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGNvbG91cj0nYmxhY2snLCBmaWxsPSdibHVlJykgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9LTkwLCBoanVzdD0xLCB2anVzdD0wLjUpCiAgKQpgYGAKCiogd2UgY2FuIG5vdyBjaGFuZ2UgdGhlIGF4ZXMgbGFiZWxzIChgbGFic2ApIGFuZCBhZGQgYSB0aXRsZSAoYGdndGl0bGVgKSwgY2hhbmdlIHRoZSBmb250IGFuZCBmb250IHNpemVzIG9mIHRoZSBheGlzIGxhYmVscwoKYGBge3IgZmlnLndpZHRoPTEwfQpnZ3Bsb3Qoc3RhdHMsIGFlcyh4PWlzb2xhdGUsIHk9cmVhZHMpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBjb2xvdXI9J2JsYWNrJywgZmlsbD0nYmx1ZScpICsKICBnZ3RpdGxlKCJSZWFkIGNvdW50cyIpICsKICBsYWJzKHg9Iklzb2xhdGUgbmFtZSIsIHk9IlJlYWQgY291bnQiKSArICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPS05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41LCBzaXplPTEwKSwKICAgIGF4aXMudGl0bGUgID0gZWxlbWVudF90ZXh0KHNpemU9MTUsIGZhbWlseT0nQ29taWMgU2FucyBNUycpCiAgKQpgYGAKCiogaWYgd2UgcHJlZmVyIHdlIGNhbiBmbGlwIHRoZSB3aG9sZSBjaGFydCAKCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0KZ2dwbG90KHN0YXRzLCBhZXMoeD1pc29sYXRlLCB5PXJlYWRzKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycsIGZpbGw9J2JsdWUnKSArCiAgZ2d0aXRsZSgiUmVhZCBjb3VudHMiKSArCiAgbGFicyh4PSJJc29sYXRlIG5hbWUiLCB5PSJSZWFkIGNvdW50IikgKyAgdGhlbWVfYncoKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0tOTAsIGhqdXN0PTEsIHZqdXN0PTAuNSkKICApICsKICBjb29yZF9mbGlwKCkKYGBgCgoqIGluIHRoZSBhYm92ZSBleGFtcGxlcyB3ZSBwcm92aWRlZCB0aGUgYHN0YXRzYCBkYXRhIG9iamVjdCB0byB0aGUgYGdncGxvdCgpYCBmdW5jdGlvbi4gVmVyeSBvZnRlbiBpdHMgZWFzaWVyIHRvIHBpcGUgdGhlIGRhdGEgaW50byB0aGUgYGdncGxvdCgpYCBmdW5jdGlvbiB1c2luZyBgJT4lYC4gQXMgeW91IGNhbiBzZWUsIHRoZSBgc3RhdHNgIHZhcmlhYmxlIG5hbWUgaXMgbm93IG91dHNpZGUgdGhlIGBnZ3Bsb3QoKWAgZnVuY3Rpb24KCmBgYHtyIGZpZy53aWR0aD0xMH0Kc3RhdHMgJT4lCiAgZ2dwbG90KGFlcyh4PWlzb2xhdGUsIHk9cmVhZHMpKSArCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGNvbG91cj0nYmxhY2snLCBmaWxsPSdibHVlJykgKwogICAgZ2d0aXRsZSgiUmVhZCBjb3VudHMiKSArCiAgICBsYWJzKHg9Iklzb2xhdGUgbmFtZSIsIHk9IlJlYWQgY291bnQiKSArICB0aGVtZV9idygpICsKICAgIHRoZW1lKAogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0tOTAsIGhqdXN0PTEsIHZqdXN0PTAuNSkKICAgICkKYGBgCgoqIFRoaXMgYWxsb3dzIHVzIHRvIG1hbmlwdWxhdGUgdGhlIGRhdGEgYmVmb3JlIHdlIHBsb3QgaXQsIGZvciBleGFtcGxlIHdlIGNhbiBhcHBseSBhIGZpbHRlciBhbmQgb25seSBwbG90IGNlcnRhaW4gc2FtcGxlcy4gYGdyZXBsKClgIGlzIGp1c3QgYSBwYXR0ZXJuIG1hdGNoaW5nIGZ1bmN0aW9uLCBzbyBmaWx0ZXJzIGlzb2xhdGVzIHdpdGggdGhlIHN0cmluZyBgU0VSYCBpbiB0aGVpciBuYW1lCgpgYGB7cn0Kc3RhdHMgJT4lCiAgZmlsdGVyKGdyZXBsKCJTRVIiLGlzb2xhdGUpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9aXNvbGF0ZSwgeT1yZWFkcykpICsKICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycsIGZpbGw9J2JsdWUnKSArCiAgICBnZ3RpdGxlKCJSZWFkIGNvdW50cyIpICsKICAgIGxhYnMoeD0iSXNvbGF0ZSBuYW1lIiwgeT0iUmVhZCBjb3VudCIpICsKICAgIHRoZW1lX2J3KCkKYGBgCgoqIG5vdyB0aGF0IHdlIGhhdmUgYSBzbWFsbGVyIHBsb3QsIHdlIGNhbiBhbHNvIGFkZCBvdGhlciB1c2VmdWwgaW5mb3JtYXRpb24sIGxpa2UgdGhlIGFjdHVhbCB2YWx1ZXMgb2YgdGhlIGJhcnMsIHVzaW5nIGBnZW9tX3RleHQoKWAKCmBgYHtyfQpzdGF0cyAlPiUKICBmaWx0ZXIoZ3JlcGwoIlNFUiIsaXNvbGF0ZSkpICU+JQogIGdncGxvdChhZXMoeD1pc29sYXRlLCB5PXJlYWRzKSkgKwogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBjb2xvdXI9J2JsYWNrJywgZmlsbD0nYmx1ZScpICsKICAgIGdndGl0bGUoIlJlYWQgY291bnRzIikgKwogICAgbGFicyh4PSJJc29sYXRlIG5hbWUiLCB5PSJSZWFkIGNvdW50IikgKwogICAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yZWFkcyksIHZqdXN0PS0wLjMsIHNpemU9My41KSArCiAgICB0aGVtZV9idygpCmBgYAoKPGhyIHN0eWxlPSJib3JkZXI6MnB4IHNvbGlkIGdyYXkiPiA8L2hyPgoKIyMgUGxvdHRpbmcgYSBzY2F0dGVyIHBsb3QKCiogTGV0cyBwbG90IGEgZGlmZmVyZW50IHR5cGUgb2YgZ3JhcGgsIHRoaXMgdGltZSBwbG90dGluZyBgcmVhZHNgIGFnYWluc3QgYGNvdmVyYWdlYAoKYGBge3J9CnN0YXRzICU+JQogIGdncGxvdChhZXMoeD1yZWFkcywgeT1jb3ZlcmFnZSkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICB0aGVtZV9idygpICsKICAgIHRoZW1lKAogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0tOTAsIGhqdXN0PTEsIHZqdXN0PTAuNSkKICAgICkgCmBgYAoKKiB3ZSBjYW4gZWFzaWx5IGFkZCBhIHNtb290aGVkIHJlZ3Jlc3Npb24gbGluZSB3aXRoIGBnZW9tX3Ntb290aCgpYAoKYGBge3J9CnN0YXRzICU+JQogIGdncGxvdChhZXMoeD1yZWFkcywgeT1jb3ZlcmFnZSkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIHRoZW1lX2J3KCkgKwogICAgdGhlbWUoCiAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPS05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KQogICAgKSAKYGBgICAgIAogIAoqIGlmIHdlIG5lZWQgdG8gd2UgY2FuIGxpbWl0IHRoZSByZWdpb24gb2YgdGhlIHBsb3QgdG8gaW5jbHVkZSwgdXNpbmcgYHhsaW0oKWAgYW5kL29yIGB5bGltKClgCgpgYGB7cn0Kc3RhdHMgJT4lCiAgZ2dwbG90KGFlcyh4PXJlYWRzLCB5PWNvdmVyYWdlKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgeGxpbShjKDAsIDYwMDAwMCkpICsgCiAgICB5bGltKGMoMCwgMTAwKSkgKwogICAgdGhlbWVfYncoKSArCiAgICB0aGVtZSgKICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9LTkwLCBoanVzdD0xLCB2anVzdD0wLjUpCiAgICApIApgYGAKCgoqIEZpbmFsbHkgaWYgd2Ugd2lzaCB3ZSBjYW4gbWFrZSBpbnRlcmFjdGl2ZSB2ZXJzaW9ucyBvZiBvdXIgcGxvdHMgdXNpbmcgdGhlIGBwbG90bHlgIHBhY2thZ2UKKiB0cnkgc2VsZWN0aW9uIHJlZ2lvbnMgb2YgdGhlIHBsb3QgYW5kIHpvb21pbmcsIHVzaW5nIHRoZSBtZW51IHJpYmJvbiBvbiB0aGUgdG9wIHJpZ2h0IG9mIHRoZSBwbG90CgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD05fQpsaWJyYXJ5KHBsb3RseSkKCmNvdmVyYWdlIDwtIHN0YXRzICU+JQogIGdncGxvdChhZXMoeD1yZWFkcywgeT1jb3ZlcmFnZSkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIHhsaW0oYygwLCA2MDAwMDApKSArIAogICAgeWxpbShjKDAsIDEwMCkpICsKICAgIHRoZW1lX2J3KCkgKwogICAgdGhlbWUoCiAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPS05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KQogICAgKSAKCmdncGxvdGx5KGNvdmVyYWdlKQpgYGAKCjxociBzdHlsZT0iYm9yZGVyOjJweCBzb2xpZCBncmF5Ij4gPC9ocj4KCiMjIEV4ZXJjaXNlIDEKCiogbG9hZCB0aGUgZGF0YXNldCBzdG9yZWQgaW4gYGRhdGEvZGF0YS5jc3ZgLCBwbG90IGEgYmFyIGNoYXJ0IG9mIGBjb3VudHlgIGFnYWluc3QgYHBvcGFkdWx0c2AgZm9yIHRoZSBzdGF0ZSBvZiBPaGlvIChPSCkKKiByZW1lbWJlciBob3cgdG8gcm90YXRlIHRoZSB4IGF4aXMgbGFiZWxzIHRvIG1ha2UgdGhlbSBtb3JlIHJlYWRhYmxlLAoqIGFkZCBhIHRpdGxlIHRvIHRoZSBwbG90IGFuZCByZW5hbWUgdGhlIGF4ZXMgdGl0bGVzCgpgYGB7cn0KCmBgYAoKKiBVc2luZyB0aGUgc2FtZSBkYXRhc2V0IGdlbmVyYXRlICBhIHNjYXR0ZXIgcGxvdCBvZiBgYXJlYWAgYWdhaW5zdCBgcG9wdG90YWxgIAoqIFRyeSBhZGRpbmcgYSByZWdyZXNzaW9uIGxpbmUgdXNpbmcgdGhlIGBnbG1gIG1ldGhvZAoqIFRyeSByZW1vdmluZyBzb21lIG9mIHRoZSBvdXRsaWVycyBmcm9tIHRoZSBwbG90LiBIaW50LCB1c2UgYHhsaW0oKWAgYW5kL29yIGB5bGltKClgCiogQ2hhbmdlIHRoZSBzaXplIGFuZCBjb2xvdXIgb2YgdGhlIHBvaW50cz8gSGludDogY2hlY2sgdGhlIGhlbHAgd2l0aCBgP2dlb21fbGluZWAKKiBDb2xvdXIgdGhlIHBvaW50cyBieSB0aGUgYHN0YXRlYCBjb2x1bW4KKiBTZXQgdGhlIHNpemUgb2YgdGhlIHBvaW50cyBhY2NvcmRpbmcgdG8gdGhlIGBwb3BkZW5zaXR5YCBjb2x1bW4KKiBNb3ZlIHRoZSBsZWdlbmQgcG9zaXRpb24gdG8gdGhlIGJvdHRvbSBvZiB0aGUgY2hhcnQuIEhpbnQ6IGNoZWNrIHRoZSBoZWxwIHdpdGggYD90aGVtZWAKKiBBZGQgdGl0bGUgYW5kIGEgc3VidGl0bGUKKiBDaGFuZ2UgdGhlIGF4ZXMgdGl0bGVzIHRvICdQb3B1bGF0aW9uIHRvdGFsJyBhbmQgJ0NvdW50eSBhcmVhJwoqIEltcHJvdmUgdGhlIGxhYmVscyBvbiB0aGUgeS1heGlzLiBIaW50OiBsb29rIGF0IGA/c2NhbGVfeV9jb250aW51b3VzYAoKYGBge3J9CgpgYGAKCiogQ2hlY2sgb3V0IHRoaXMgc2l0ZSBmb3IgbG90cyBvZiBvdGhlciBleGFtcGxlIHBsb3RzCmh0dHA6Ly9yLXN0YXRpc3RpY3MuY28vVG9wNTAtR2dwbG90Mi1WaXN1YWxpemF0aW9ucy1NYXN0ZXJMaXN0LVItQ29kZS5odG1sCgo8aHIgc3R5bGU9ImJvcmRlcjoycHggc29saWQgZ3JheSI+IDwvaHI+CgojIyBQbG90dGluZyBib3hwbG90cwoKKiBXZSB3aWxsIHVzZSBvbmUgb2YgdGhlIGJ1aWx0IGluIGRhdGFzZXRzIGZvciB0aGlzIHNlY3Rpb24sIHRoZSBkYXRhc2V0IHNob3dzIHRoZSBlZmZlY3Qgb2YgVml0YW1pbiBDIG9uIHRvb3RoIGdyb3d0aCB2aWEgb3JhbmdlIGp1aWNlIChPSikgb3IgYXNjb3JiaWMgYWNpZCAoVkMpLiBFeHBsb3JlIHRoZSBkYXRhCgpgYGB7cn0KVG9vdGhHcm93dGgKYGBgCgoqIExldHMgZG8gYW4gaW5pdGlhbCBzY2F0dGVyIHBsb3Qgb2YgYGRvc2VgIGFnYWluc3QgYGxlbmAgY29sb3VyaW5nIGJ5IGBzdXBwYAoKYGBge3J9ClRvb3RoR3Jvd3RoICU+JQogIGdncGxvdChhZXMoeD1kb3NlLCB5PWxlbiwgY29sb3I9c3VwcCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICB0aGVtZV9idygpCmBgYAoKKiB0aGlzIGlzIG5vdCB0aGF0IGVhc3kgdG8gdmlzdWFsaXNlIHNvIGxldHMgaml0dGVyIChzZXBhcmF0ZSkgdGhlIHBvaW50cyBhIGxpdHRsZSBieSByZXBsYWNpbmcgYGdlb21fcG9pbnRgIHdpdGggYGdlb21faml0dGVyYAoKYGBge3J9ClRvb3RoR3Jvd3RoICU+JQogIGdncGxvdChhZXMoeD1kb3NlLCB5PWxlbiwgZ3JvdXA9c3VwcCwgY29sb3I9c3VwcCkpICsKICAgIGdlb21faml0dGVyKHdpZHRoPTAuMSkgKwogICAgdGhlbWVfYncoKQpgYGAKCiogSW4gb3JkZXIgdG8gcGxvdCBhIGJveHBsb3QgdGhlIHgtYXhpcyBuZWVkcyB0byBiZSBjYXRlZ29yaWNhbCBkYXRhLCBzbyB3ZSBjYW4gY29udmVydCBvdXIgYGRvc2VgIHZhbHVlcyB0byBjaGFyYWN0ZXJzIHVzaW5nIHRoZSBgbXV0YXRlKClgIGFuZCBgYXMuY2hhcmFjdGVyKClgIGZ1bmN0aW9ucwoKYGBge3J9ClRvb3RoR3Jvd3RoICU+JQogIG11dGF0ZShkb3NlPWFzLmNoYXJhY3Rlcihkb3NlKSkgJT4lCiAgZ2dwbG90KGFlcyh4PWRvc2UsIHk9bGVuLCBjb2xvcj1zdXBwKSkgKwogICAgZ2VvbV9ib3hwbG90KGNvbG9yPSdibGFjaycpICsKICAgIHRoZW1lX2J3KCkKYGBgCgoqIFNvbWV0aW1lcyBpdCBpcyB1c2VmdWwgdG8ga2VlcCB0aGUgaW5kaXZpZHVhbCBwb2ludHMgb24gdGhlIHBsb3QsIHdlIGNhbiBqdXN0IGFkZCBiYWNrIHRoZSBqaXR0ZXJlZCBsYXllcgoKYGBge3J9ClRvb3RoR3Jvd3RoICU+JQogIG11dGF0ZShkb3NlPWFzLmNoYXJhY3Rlcihkb3NlKSkgJT4lCiAgZ2dwbG90KGFlcyh4PWRvc2UsIHk9bGVuLCBjb2xvcj1zdXBwKSkgKwogICAgZ2VvbV9ib3hwbG90KGNvbG9yPSdibGFjaycpICsKICAgIGdlb21faml0dGVyKHdpZHRoPTAuMSkgICsKICAgIHRoZW1lX2J3KCkKYGBgCgoqIHdlIGNvdWxkIGV2ZW4gZnVydGhlciBicmVhayB0aGUgYm94cGxvdHMgZG93biBieSB0aGUgYHN1cHBgIHZhcmlhYmxlLCBieSByZW1vdmluZyBgY29sb3I9ImJsYWNrImAgZnJvbSBgZ2VvbV9ib3hwbG90KClgCgpgYGB7cn0KVG9vdGhHcm93dGggJT4lCiAgbXV0YXRlKGRvc2U9YXMuY2hhcmFjdGVyKGRvc2UpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9ZG9zZSwgeT1sZW4sIGNvbG9yPXN1cHApKSArCiAgICBnZW9tX2JveHBsb3QoKSArCiAgICBnZW9tX2ppdHRlcih3aWR0aD0wLjEpICArCiAgICB0aGVtZV9idygpCmBgYAoKKiBsZXRzIHRpZHkgdXAgdGhlIHBsb3QsIGJ5IGxhYmVsaW5nIHRoZSBheGVzIGFuZCB0aGUgbGVnZW5kIHByb3Blcmx5CgpgYGB7cn0KVG9vdGhHcm93dGggJT4lCiAgbXV0YXRlKGRvc2U9YXMuY2hhcmFjdGVyKGRvc2UpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9ZG9zZSwgeT1sZW4sIGNvbG9yPXN1cHApKSArCiAgICBnZW9tX2JveHBsb3QoY29sb3I9J2JsYWNrJykgKwogICAgZ2VvbV9qaXR0ZXIod2lkdGg9MC4xKSAgKwogICAgbGFicyh4PSJWaXRhbWluIEMgZG9zZSIsIHk9IlRvb3RoIGxlbmd0aCIpICsKICAgIHRoZW1lX2J3KCkgKwogICAgdGhlbWUoCiAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSAgICAjIHRoaXMgcmVtb3ZlcyB0aGUgbGVnZW5kIHRpdGxlCiAgICApCmBgYAoKKiBBbmQgaWYgd2Ugd2FudCB0byBhZGQgc29tZSBzaWduaWZpY2FuY2UgbGFiZWxzLCB3ZSBjYW4gbWFrZSB1c2Ugb2YgdGhlIGBnZ3NpZ25pZmAgcGFja2FnZQoKYGBge3J9CmxpYnJhcnkoZ2dzaWduaWYpCgpUb290aEdyb3d0aCAlPiUKICBtdXRhdGUoZG9zZT1hcy5jaGFyYWN0ZXIoZG9zZSkpICU+JQogIGdncGxvdChhZXMoeD1kb3NlLCB5PWxlbiwgY29sb3I9c3VwcCkpICsKICAgIGdlb21fYm94cGxvdChjb2xvcj0nYmxhY2snKSArCiAgICBnZW9tX2ppdHRlcih3aWR0aD0wLjEpICArCiAgICBsYWJzKHg9IlZpdGFtaW4gQyBkb3NlIiwgeT0iVG9vdGggbGVuZ3RoIikgKwogICAgdGhlbWVfYncoKSArCiAgICB0aGVtZSgKICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpICAgICMgdGhpcyByZW1vdmVzIHRoZSBsZWdlbmQgdGl0bGUKICAgICkgKwogICAgeWxpbSgwLDQwKSArCiAgICBnZW9tX3NpZ25pZihjb21wYXJpc29ucyA9IGxpc3QoYygiMC41IiwgIjEiKSksIG1hcF9zaWduaWZfbGV2ZWwgPSBUUlVFLCB0ZXh0c2l6ZSA9IDYsIHlfcG9zaXRpb24gPSAzMCwgY29sb3VyPSJibGFjayIsIGFubm90YXRpb249YygnKionKSkgKwogICAgZ2VvbV9zaWduaWYoY29tcGFyaXNvbnMgPSBsaXN0KGMoIjEiLCAiMiIpKSwgbWFwX3NpZ25pZl9sZXZlbCA9IFRSVUUsIHRleHRzaXplID0gNiwgeV9wb3NpdGlvbiA9IDM2LCBjb2xvdXI9ImJsYWNrIiwgYW5ub3RhdGlvbj1jKCcqJykpIApgYGAKCjxociBzdHlsZT0iYm9yZGVyOjJweCBzb2xpZCBncmF5Ij4gPC9ocj4KCiMjIFBsb3R0aW5nIGxpbmUgcGxvdHMKCiogVXNpbmcgdGhlIHNhbWUgdG9vdGggZ3Jvd3RoIGRhdGFzZXQsIGluIG9yZGVyIHRvIGRyYXcgYSBsaW5lIHBsb3Qgd2UgbmVlZCB0byBjYWxjdWxhdGUgdGhlIG1lYW5zIGFuZCBzdGFuZGFyZCBkZXZpYXRpb25zIG9mIHRoZSBkYXRhLiBSZW1lbWJlciB0aGUgZGF0YSBsb29rcyBsaWtlIHRoaXMKCmBgYHtyfQpUb290aEdyb3d0aApgYGAKKiB3ZSBjYW4gc3VtbWFyaXNlIHRoZSBkYXRhIGJ5IGdyb3VwaW5nIHRoZSB2YXJpYWJsZXMgYW5kIGNhbGN1bGF0aW5nIHRoZSBtZWFuL3NkIG92ZXIgdGhlc2UgdmFyaWFibGVzCgpgYGB7cn0Kc3VtbWFyeSA8LSBUb290aEdyb3d0aCAlPiUKICBncm91cF9ieShzdXBwLCBkb3NlKSAlPiUKICBzdW1tYXJpc2UobWVhbj1tZWFuKGxlbiksIHNkPXNkKGxlbikpCgpzdW1tYXJ5CmBgYAoKKiBpdCBpcyBub3cgc3RyYWlnaHRmb3J3YXJkIHRvIGdlbmVyYXRlIGEgbGluZSBwbG90CgpgYGB7cn0Kc3VtbWFyeSAlPiUKICBnZ3Bsb3QoYWVzKHg9ZG9zZSwgeT1tZWFuLCBjb2xvcj1zdXBwKSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgdGhlbWVfYncoKQpgYGAKKiB3ZSBjYW4gYWxzbyB1c2UgdGhlIGNhbGN1bGF0ZWQgc3RhbmRhcmQgZGV2aWF0aW9ucyB0byBhZGQgZXJyb3IgYmFycwoKYGBge3J9CnN1bW1hcnkgJT4lCiAgZ2dwbG90KGFlcyh4PWRvc2UsIHk9bWVhbiwgY29sb3I9c3VwcCkpICsKICAgIGdlb21fbGluZSgpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPW1lYW4tc2QsIHltYXg9bWVhbitzZCksIHdpZHRoPS4wNSkgKwogICAgdGhlbWVfYncoKQpgYGAKCiogdGhlIGVycm9yIGJhcnMgb3ZlcmxhcCBzbyB3ZSBjYW4gYWN0dWFsbHkgc2hpZnQgKGRvZGdlKSB0aGVtIHNsaWdodGx5CgpgYGB7cn0Kc3VtbWFyeSAlPiUKICBnZ3Bsb3QoYWVzKHg9ZG9zZSwgeT1tZWFuLCBjb2xvcj1zdXBwKSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fZXJyb3JiYXIoYWVzKHltaW49bWVhbi1zZCwgeW1heD1tZWFuK3NkKSwgd2lkdGg9LjA1LAogICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKDAuMDUpKSArCiAgICB0aGVtZV9idygpCmBgYAoKPGhyIHN0eWxlPSJib3JkZXI6MnB4IHNvbGlkIGdyYXkiPiA8L2hyPgoKIyMgRXhlcmNpc2UgMgoKKiBXZSB3aWxsIHVzZSBhbm90aGVyIGRlZmF1bHQgZGF0YXNldCwgYG10Y2Fyc2AuIEZpcnN0IHRha2UgYSBsb29rIGFuZCBleHBsb3JlCgpgYGB7cn0KCmBgYAoKKiBQbG90IGEgYm94cGxvdCB3aXRoIGBjeWxgIG9uIHRoZSB4LWF4aXMgYW5kIGBtcGdgIG9uIHRoZSB5LWF4aXMKKiBpbmNsdWRlIHRoZSByYXcgZGF0YSBwb2ludHMsIGppdHRlcmVkIGFuZCBjb2xvdXJlZCBieSBjYXJiCiogVGlkeSB1cCB0aGUgYXhlcyBsYWJlbHMgYW5kIHJlbW92ZSB0aGUgbGVnZW5kIHRpdGxlCgpgYGB7cn0KCmBgYAoKKiBOb3cgcGxvdCBhIGxpbmUgZ3JhcGggc2hvd2luZyB0aGUgbWVhbiBhbmQgdGhlIHN0YW5kYXJkIGRldmlhdGlvbnMgb2YgdGhlIGBtcGdgIHZhbHVlcywgY29sb3VyZWQgYnkgdGhlIG51bWJlciBvZiBjYXJidXJldG9ycwoKYGBge3J9CgpgYGAKCiogUGxvdCBhIHNpbWlsYXIgcGxvdCBjb21wYXJpbmcgdGhlIG51bWJlciBvYyBjeWxpbmRlcnMgd2l0aCB0aGUgbXBnIGJ1dCBjb2xvdXJlZCBieSB0aGUgbnVtYmVyIG9mIGdlYXJzCgpgYGB7cn0KCmBgYAoKPGhyIHN0eWxlPSJib3JkZXI6MnB4IHNvbGlkIGdyYXkiPiA8L2hyPgoKIyMgUGxvdHRpbmcgZGVuZHJvZ3JhbXMKCiogV2UgY2FuIGVhc2lseSBwZXJmb3JtIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9uIGEgZGF0YXNldCBhbmQgdGhlbiBkcmF3IGEgdHJlZQoqIGhlcmUgd2UgdXNlIHRoZSBidWlsdCBpbiBkYXRhc2V0IG9mIGBVU2FycmVzdHNgLCBmaXJzdCBleHBsb3JlIHRoZSBkYXRhCgpgYGB7cn0KVVNBcnJlc3RzCmBgYAoKKiBOb3cgd2UgY2FuIHBlcmZvcm0gdGhlIGNsdXN0ZXJpbmcgdXNpbmcgdGhlIGBoY2x1c3RgIGZ1bmN0aW9uLCB0aGlzIHVzZXMgZXVjbGlkZWFuIGRpc3RhbmNlcyBieSBkZWZhdWx0CiogQnV0IHRoZSBkaXN0YW5jZSBvYmplY3QgKGBkaXN0KFVTQXJyZXN0cylgKSBjYW4gYmUgYW55IGtpbmQgb2Ygc2ltaWxhcml0eSBtYXRyaXgsIGUuZyBwZWFyc29uIGNvcnJlbGF0aW9ucwoKYGBge3J9CmhjIDwtIGhjbHVzdChkaXN0KFVTQXJyZXN0cyksICJhdmUiKSAgIyBoaWVyYXJjaGljYWwgY2x1c3RlcmluZwpoYwpgYGAKCiogTm93IHdlIGRyYXcgdGhlIHRyZWUgdXNpbmcgIHRoZSBgZ2dkZW5kcm9ncmFtKClgIGZ1bmN0aW9uIGZyb20gdGhlIGBgZ2dkZW5kcm9gIHBhY2thZ2UKCmBgYHtyIGZpZy5oZWlnaHQ9N30KbGlicmFyeShnZ2RlbmRybykKCnAgPC0gZ2dkZW5kcm9ncmFtKGhjLCByb3RhdGUgPSBUUlVFLCBzaXplID0gMikKcApgYGAKCiogcmVtZW1iZXIgdGhhdCB3ZSBjYW4gZWFzaWx5IG1ha2UgdGhlIHBsb3QgaW50ZXJhY3RpdmUgd2l0aCBgZ2dwbG90bHkoKWAKCmBgYHtyfQpnZ3Bsb3RseShwKQpgYGAKCjxociBzdHlsZT0iYm9yZGVyOjJweCBzb2xpZCBncmF5Ij4gPC9ocj4KCiMjIENvbWJpbmluZyBwbG90cyBpbnRvIGEgZmlndXJlCgoqIEluIG9yZGVyIHRvIGNvbWJpbmUgcGxvdHMgaW50byBhIHB1YmxpY2F0aW9uIHJlYWR5IGZpZ3VyZSwgbGV0cyBwbG90IGJvdGggdGhlIHByZXZpb3VzIHJlYWRzIGFuZCB0aGUgY292ZXJhZ2UgcGxvdHMsIG9ubHkgdGhpcyB0aW1lIHdlIHdpbGwgc3RvcmUgdGhlIHBsb3RzIGluIHRoZSB2YXJpYWJsZXMgYHJlYWRzYCBhbmQgYGNvdmVyYWdlYCwgbm90aWNlIHRoYXQgdGhleSB3b24ndCBiZSBwcmludGVkIHdoZW4geW91IHJ1biB0aGUgY29kZQoKYGBge3J9CnJlYWRzIDwtIHN0YXRzICU+JQogIGdncGxvdChhZXMoeD1pc29sYXRlLCB5PXJlYWRzKSkgKwogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBjb2xvdXI9J2JsYWNrJywgZmlsbD0nYmx1ZScpICsKICAgIGdndGl0bGUoIlJlYWQgY291bnRzIikgKwogICAgbGFicyh4PSJJc29sYXRlIG5hbWUiLCB5PSJSZWFkIGNvdW50IikgKyAgdGhlbWVfYncoKSArCiAgICB0aGVtZSgKICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9LTkwLCBoanVzdD0xLCB2anVzdD0wLjUpCiAgICApCgpjb3ZlcmFnZSA8LSBzdGF0cyAlPiUKICBnZ3Bsb3QoYWVzKHg9aXNvbGF0ZSwgeT1jb3ZlcmFnZSkpICsKICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycsIGZpbGw9JyM4ODg4ODgnKSArCiAgICBnZ3RpdGxlKCJDb3ZlcmFnZSIpICsKICAgIGxhYnMoeD0iSXNvbGF0ZSBuYW1lIiwgeT0iQ292ZXJhZ2UiKSArCiAgICB0aGVtZV9idygpICsKICAgIHRoZW1lKAogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0tOTAsIGhqdXN0PTEsIHZqdXN0PTAuNSkKICAgICkKYGBgCgoqIGJ1dCB3ZSBjYW4gc2hvdyB0aGUgcGxvdHMgYnkganVzdCAicnVubmluZyIgdGhlIHZhcmlhYmxlIG5hbWVzCgpgYGB7cn0KcmVhZHMKY292ZXJhZ2UKYGBgCgoqIHdlIGNhbiBub3cgbWFrZSBhIGNvbWJpbmVkIHBsb3QgdXNpbmd0IHRoZSBgcGF0Y2h3b3JrYCBwYWNrYWdlCiogZmlyc3RseSBzaWRlIGJ5IHNpZGUgd2l0aCB0aGUgJysnIG9wZXJhdG9yCgpgYGB7cn0KbGlicmFyeShwYXRjaHdvcmspCgpyZWFkcyArIGNvdmVyYWdlCmBgYAoKKiBpZiB3ZSB3YW50IHRoZW0gYWJvdmUgYW5kIGJlbG93IHdlIGNoYW5nZSB0aGUgJysnIG9wZXJhdG9yIHRvICcvJwoKYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTIwfQpyZWFkcyAvIGNvdmVyYWdlCmBgYAoKKiBXZSBjYW4gYWRkIHN1YnBsb3QgbGFiZWxzIChBLCBCIGV0YykKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0yMH0KcmVhZHMgLyBjb3ZlcmFnZSArIHBsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKSArIHBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikKYGBgCgoqIE5vdGUgdGhhdCB3ZSBjYW4gaW5jbHVkZSBtb3JlIHBsb3RzIGluIHRoZSBmaWd1cmUuIEhlcmUgd2UgZ2VuZXJhdGUgZm91ciB2ZXJzaW9ucyBvZiB0aGUgYHJlYWRzYCBwbG90IGJ1dCBjaGFuZ2luZyB0aGUgdW5kZXJseWluZyB0aGVtZS4gV2UgY2FuIGFsc28gYWRkIGEgbmV3IHRpdGxlLCBub3RlIHRoYXQgdGhpcyBpcyBvdmVycmlkaW5nIHRoZSBgZ2d0aXRsZSgpYCBhbHJlYWR5IGluIHRoZSBgcmVhZHNgIHBsb3QuIENhbiB5b3Ugc3BvdCB0aGUgZGlmZmVyZW5jZXMgaW4gdGhlIHRoZW1lcz8KCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0yMH0KCnAxIDwtIHJlYWRzICsgdGhlbWVfYncoKSArIGdndGl0bGUoJ3RoZW1lX2J3KCknKQpwMiA8LSByZWFkcyArIHRoZW1lX2NsYXNzaWMoKSArIGdndGl0bGUoJ3RoZW1lX2NsYXNzaWMoKScpCnAzIDwtIHJlYWRzICsgdGhlbWVfbWluaW1hbCgpICsgZ2d0aXRsZSgndGhlbWVfbWluaW1hbCgpJykKcDQgPC0gcmVhZHMgKyB0aGVtZV9kYXJrKCkgKyBnZ3RpdGxlKCd0aGVtZV9kYXJrKCknKQoKKCBwMSArIHAyICkgLyAoIHAzICsgcDQgKSArIHBsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKQpgYGAKCjxociBzdHlsZT0iYm9yZGVyOjJweCBzb2xpZCBncmF5Ij4gPC9ocj4KCiMjIEV4ZXJjaXNlIDMKCiogR2VuZXJhdGUgYSBjb21iaW5lZCBmaWd1cmUgaW5jbHVkaW5nIGEgYm94cGxvdCBhbmQgYSBsaW5lcGxvdCBjb21wYXJpbmcgdGhlIG51bWJlciBvZiBjeWxpbmRlcnMgd2l0aCB0aGUgbXBnIGFzIGdlbmVyYXRlZCBhYm92ZSwgbGFiZWwgdGhlIHN1YnBsb3RzIHVzaW5nIGEsIGIKCmBgYHtyfQoKYGBgCgo8aHIgc3R5bGU9ImJvcmRlcjoycHggc29saWQgZ3JheSI+IDwvaHI+CgoKQWRkIGEgbmV3IGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqSW5zZXJ0IENodW5rKiBidXR0b24gb24gdGhlIHRvb2xiYXIgb3IgYnkgcHJlc3NpbmcgKkN0cmwrQWx0K0kqLgoKV2hlbiB5b3Ugc2F2ZSB0aGUgbm90ZWJvb2ssIGFuIEhUTUwgZmlsZSBjb250YWluaW5nIHRoZSBjb2RlIGFuZCBvdXRwdXQgd2lsbCBiZSBzYXZlZCBhbG9uZ3NpZGUgaXQgKGNsaWNrIHRoZSAqUHJldmlldyogYnV0dG9uIG9yIHByZXNzICpDdHJsK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuCgpUaGUgcHJldmlldyBzaG93cyB5b3UgYSByZW5kZXJlZCBIVE1MIGNvcHkgb2YgdGhlIGNvbnRlbnRzIG9mIHRoZSBlZGl0b3IuIENvbnNlcXVlbnRseSwgdW5saWtlICpLbml0KiwgKlByZXZpZXcqIGRvZXMgbm90IHJ1biBhbnkgUiBjb2RlIGNodW5rcy4gSW5zdGVhZCwgdGhlIG91dHB1dCBvZiB0aGUgY2h1bmsgd2hlbiBpdCB3YXMgbGFzdCBydW4gaW4gdGhlIGVkaXRvciBpcyBkaXNwbGF5ZWQuCgo8aHIgc3R5bGU9ImJvcmRlcjoycHggc29saWQgZ3JheSI+IDwvaHI+CgojIFNlc3Npb24gZGV0YWlscwoKKiBHZW5lcmF0ZSBkb2N1bWVudCB2ZXJzaW9uIGRldGFpbHMKCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAo=